﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//#if UNITY_EDITOR
//using UnityEditor;
//#endif

class CoroutineHelper: MonoBehaviour
{
	
}

class PoolInfo
{
	public int maxVisibleNum = 0;
	public int curVisibleNum = 0;
	public Queue<Object> pool = new Queue<Object>();

	public void AddVisibleNum()
	{
		curVisibleNum++;
		maxVisibleNum = Mathf.Max(curVisibleNum, maxVisibleNum);
	}

	public void SubVisibleNum()
	{
		curVisibleNum--;
		if (curVisibleNum < 0)
		{
			Debug.LogError("curVisibleNum < 0: " + curVisibleNum);

			curVisibleNum = 0;
		}
	}

	public void Destroy(bool all = false)
	{
		int totalNum = curVisibleNum + pool.Count;
		int destroyNum = totalNum - maxVisibleNum;
        if (all)
        {
            destroyNum = pool.Count;
        }
            
		while (destroyNum-- > 0)
		{
			Object obj = pool.Dequeue();
            if(obj is Component)
            {
                obj = ((Component)obj).gameObject;
            }

            if (Application.isPlaying)
            {
                GameObject.Destroy(obj);
            }
            else
            {
                GameObject.DestroyImmediate(obj);
            }
		}

		maxVisibleNum = curVisibleNum;
	}
}

public static class ObjectPool
{
	static bool disableRecycle = false;
	static Dictionary<object, PoolInfo> recycledObjPoolMap = new Dictionary<object, PoolInfo>();
	static Dictionary<int, PoolInfo> objToPoolMap = new Dictionary<int, PoolInfo>();
	static GameObject recycledObjParent;
	static GameObject newObjParent;
	static MonoBehaviour coroutineHelper;
	static bool recycleSetPosition = false;
	static Vector3 recycledPosition = new Vector3(0, -10000, 0);
    static Dictionary<string, HashSet<System.Action<Object, Object>>> pathCallbackMap = new Dictionary<string, HashSet<System.Action<Object, Object>>>();
    static int destroyWaitTime = 30;

    public const int UnloadWay_None = 0;
    public const int UnloadWay_UnloadUnusedAssets = 1;
    public const int UnloadWay_UnloadAsset = 2;
    static int unloadWay = UnloadWay_None;

    public static void SetRecycleBySetPosition(bool enable)
    {
        recycleSetPosition = enable;
    }

    public static void SetDestroyWaitTime(int time)
    {
        destroyWaitTime = time;
    }

    public static void SetUnloadWay(int way)
    {
        unloadWay = way;
    }

    public static void Reset()
    {
        var e = recycledObjPoolMap.GetEnumerator();
        while (e.MoveNext())
        {
            e.Current.Value.Destroy(true);
        }
        recycledObjPoolMap.Clear();
    }

	public static void MoveToRecycledPosition(GameObject obj)
	{
		if (obj == null)
		{
			return;
		}

		obj.transform.position = recycledPosition;
	}

    static GameObject GetRecycleObjParent()
    {
        if (recycledObjParent == null)
        {
            recycledObjParent = new GameObject("recycled_obj_parent");

            if (Application.isPlaying)
            {
                recycledObjParent.hideFlags = HideFlags.NotEditable;
                GameObject.DontDestroyOnLoad(recycledObjParent);
            }
            else
            {
                recycledObjParent.hideFlags = HideFlags.NotEditable | HideFlags.HideAndDontSave;
            }

            coroutineHelper = recycledObjParent.AddComponent<CoroutineHelper>();
            coroutineHelper.StartCoroutine(DestroyTimer());
        }
        return recycledObjParent;

    }

	static ObjectPool()
	{
		newObjParent = new GameObject("new_obj_parent");

        if (Application.isPlaying)
        {
            newObjParent.hideFlags = HideFlags.NotEditable;
            GameObject.DontDestroyOnLoad(newObjParent);
        }
        else
        {
            newObjParent.hideFlags = HideFlags.NotEditable | HideFlags.HideAndDontSave;
        }

		
	}

	static IEnumerator DestroyTimer()
	{
		while (true)
		{
            yield return new WaitForSeconds(destroyWaitTime);

			Dictionary<object, PoolInfo>.Enumerator enumerator = recycledObjPoolMap.GetEnumerator();
			while (enumerator.MoveNext())
			{
				enumerator.Current.Value.Destroy();
			}

            if(unloadWay == UnloadWay_UnloadUnusedAssets)
            {
    			AsyncOperation op = Resources.UnloadUnusedAssets();
    			while (!op.isDone)
    			{
    				yield return null;
    			}
            }
            else if(unloadWay == UnloadWay_UnloadAsset)
            {
//                Resources.UnloadAsset("");
            }
		}
	}

	public static Object PopFromPool(object key)
	{
		PoolInfo pool = null;
		return PopFromPool(key, out pool);
	}

	static Object PopFromPool(object key, out PoolInfo pool)
	{
		pool = null;
		if (recycledObjPoolMap.TryGetValue(key, out pool) && pool.pool.Count > 0)
		{
			Object retObj = pool.pool.Dequeue();
			objToPoolMap[retObj.GetInstanceID()] = pool;
			pool.AddVisibleNum();
			return retObj;
		}
		return null;
	}

	static PoolInfo CreatePool(object key)
	{
		PoolInfo pool = null;
		if(!recycledObjPoolMap.TryGetValue(key, out pool))
		{
			pool = new PoolInfo();
			recycledObjPoolMap.Add(key, pool);
		}
		return pool;
	}

    public static void CancelAsyncLoad(string path, System.Action<Object, Object> callback)
    {
        if (string.IsNullOrEmpty(path) || callback == null)
        {
            return;
        }
        
        HashSet<System.Action<Object, Object>> callbackSet = null;
        if (pathCallbackMap.TryGetValue(path, out callbackSet))
        {
            callbackSet.Remove(callback);
            if (callbackSet.Count <= 0)
            {
                pathCallbackMap.Remove(path);

                ResourceManager.CancelAsyncTask(path, LoadAssetAsyncCallback);
            }
        }
    }

    public static void LoadAssetAsync(string path, System.Action<Object, Object> callback)
    {
        LoadAssetAsync(path, 0, callback);
    }

    public static void LoadAssetAsync(string path, int priority, System.Action<Object, Object> callback)
	{
		if (string.IsNullOrEmpty(path))
		{
			callback(null, null);
			return;
		}
            
		PoolInfo pool = null;
		Object retObj = PopFromPool(path, out pool);
		if(retObj != null)
		{
			if (retObj is GameObject)
			{
				GameObject obj = retObj as GameObject;
//					obj.transform.SetParent(null, false);
				if (!recycleSetPosition)
				{
					obj.SetActive(true);
				}
                else
                {
                    if(!obj.activeSelf)
                    {
                        obj.SetActive(true);
                    }
                }
			}

			callback(null, retObj);
		}
		else
		{
            bool first = false;
            HashSet<System.Action<Object, Object>> callbackSet = null;
            if (!pathCallbackMap.TryGetValue(path, out callbackSet))
            {
                callbackSet = new HashSet<System.Action<Object, Object>>();
                pathCallbackMap.Add(path, callbackSet);
                first = true;
            }

            callbackSet.Add(callback);

            if (first)
            {
                ResourceManager.LoadAssetAsync(path, priority, LoadAssetAsyncCallback);
            }
		}
	}
    
    static void LoadAssetAsyncCallback(object prefabObj, string path)
    {
        Object prefab = prefabObj as Object;

        HashSet<System.Action<Object, Object>> callbackSet = null;
        if (pathCallbackMap.TryGetValue(path, out callbackSet))
        {
            pathCallbackMap.Remove(path);

            HashSet<System.Action<Object, Object>>.Enumerator e = callbackSet.GetEnumerator();
            while (e.MoveNext())
            {
                Object obj = null;
                if (prefab != null && prefab.GetType() == typeof(UnityEngine.GameObject))
                {
                    PoolInfo pool = CreatePool(path);
                    obj = GameObject.Instantiate(prefab, newObjParent.transform);
                    LinkToPool(path, obj, pool);
                }

                e.Current(prefab, obj);
            }
        }
    }

	public static Object Instantiate(Object prefab)
	{
		return Instantiate(prefab, null);
	}

	public static Object Instantiate(Object prefab, Transform parent)
	{
		if (prefab == null)
		{
			return null;
		}

		int key = prefab.GetInstanceID();
		PoolInfo pool = null;
		Object retObj = PopFromPool(key, out pool);
		if(retObj != null)
		{
			if (retObj is GameObject)
			{
				GameObject obj = retObj as GameObject;
				obj.transform.SetParent(parent, false);
				if (!recycleSetPosition)
				{
					obj.SetActive(true);
				}
                else
                {
                    if(!obj.activeSelf)
                    {
                        obj.SetActive(true);
                    }
                }
			}
			return retObj;
		}
		else
		{
//            #if UNITY_EDITOR
//            if (!Application.isPlaying && prefab is GameObject)
//            {
//                retObj = PrefabUtility.InstantiatePrefab(prefab);
//                GameObject obj = retObj as GameObject;
//                obj.SetActive(false);
//                obj.SetActive(true);
//
//                if(parent != null)
//                {
//                    obj.transform.SetParent(parent);
//                }
//            }
//            else
//            #endif
            {
                retObj = UnityEngine.GameObject.Instantiate(prefab, parent);
            }

			LinkToPool(key, retObj, pool);
			return retObj;
		}
	}

	public static GameObject NewGameObject()
	{
		return NewGameObject(0);
	}

	public static GameObject NewGameObject(object key)
	{
		if(key == null)
		{
			key = 0;
		}

		PoolInfo pool = null;
		Object retObj = PopFromPool(key, out pool);
		if(retObj != null)
		{
			GameObject obj = retObj as GameObject;
			obj.transform.SetParent(null, false);
			obj.transform.localPosition = Vector3.zero;
			obj.transform.localRotation = Quaternion.identity;
			obj.transform.localScale = Vector3.one;
			if (!recycleSetPosition)
			{
				obj.SetActive(true);
			}
            else
            {
                if(!obj.activeSelf)
                {
                    obj.SetActive(true);
                }
            }
			return obj;
		}
		else
		{
			GameObject obj = new GameObject();
			LinkToPool(key, obj, pool);
			return obj;
		}
	}

	public static void LinkToPool(object key, Object obj)
	{
		LinkToPool(key, obj, null);
	}

	static void LinkToPool(object key, Object obj, PoolInfo pool)
	{
		PoolInfo oldPoolInfo = null;
		if (objToPoolMap.TryGetValue(obj.GetInstanceID(), out oldPoolInfo) && oldPoolInfo != null)
		{
			return;
		}

		if (pool == null)
		{
			pool = CreatePool(key);
		}
		objToPoolMap[obj.GetInstanceID()] = pool;
		pool.AddVisibleNum();
	}

	public static void Recycle(Object obj)
	{
		Recycle(obj, 0);
	}

	public static void Recycle(Object obj, float delay)
	{
		if(obj == null)
		{
			return;
		}

		GetRecycleObjParent();
		if(delay > 0 && coroutineHelper != null)
		{
			coroutineHelper.StartCoroutine(DelayRecycle(obj, delay));
		}
		else
		{
			RealRecycle(obj);
		}
	}

	static IEnumerator DelayRecycle(Object obj, float delay)
	{
		yield return new WaitForSeconds(delay);
		RealRecycle(obj);
	}

	static bool canRecycle(Object obj)
	{
		if(disableRecycle)
		{
			return false;
		}

        GameObject rop = GetRecycleObjParent();
        if (rop == null || rop.transform == null)
		{
			return false;
		}

		if (obj == null)
		{
			return false;
		}

		if(obj is GameObject)
		{
			GameObject temp = obj as GameObject;
			if (temp.transform == null)
			{
				return false;
			}
		}

		return true;
	}


	static void RealRecycle(Object obj)
	{
		if (obj == null)
		{
			return;
		}

		PoolInfo pool = null;
		if (objToPoolMap.TryGetValue(obj.GetInstanceID(), out pool) && pool != null)
		{
			objToPoolMap.Remove(obj.GetInstanceID());

			bool recycle = canRecycle(obj);
			if (recycle)
			{
				if (obj is GameObject)
				{
					GameObject temp = obj as GameObject;

                    GameObject rop = GetRecycleObjParent();

                    // if(rop.transform.parent != null)
                    // {
                    //     Debug.LogError("test_setobjectPool begin "+ temp + " | " + rop.transform.parent + " | "  + rop.transform);

                    //     rop.transform.SetParent(null, false);
                    //     rop.layer = 0;
                    //     GameObject.DontDestroyOnLoad(rop);
                    // }

//                    if(!temp.activeSelf)
//                    {
//                        Debug.LogError("test_setobjectPool not active: " + temp);
//                    }

					if (recycleSetPosition)
					{
						GameObject isNeedSetActiveGamObject = null;
						if(temp.transform.parent != null && temp.transform.parent.gameObject.activeSelf == false){
							isNeedSetActiveGamObject = temp.transform.parent.gameObject;
							temp.transform.parent.gameObject.SetActive(true);
						}
						
                        temp.transform.SetParent(rop.transform, false);
						temp.transform.position = recycledPosition;
						if (isNeedSetActiveGamObject != null){
							isNeedSetActiveGamObject.SetActive(false);
						}
						//Debug.Log("RealRecycle" + temp + " | " + rop.transform.parent + " | "  + rop.transform);
					}
					else
					{
						temp.SetActive(false);
                        temp.transform.SetParent(rop.transform, false);
					}

                    // if(rop.transform.parent != null)
                    // {
                    //     Debug.LogError("test_setobjectPool end " + temp + " | " + rop.transform.parent + " | "  + rop.transform);

                    //     rop.transform.SetParent(null, false);
                    //     rop.layer = 0;
                    //     GameObject.DontDestroyOnLoad(rop);
                    // }
				}


				pool.pool.Enqueue(obj);
			}
			else
			{
				if (obj is GameObject)
				{
					GameObject.Destroy(obj);
				}
			}

			pool.SubVisibleNum();
		}
		else
		{
			//logError("recycle failed: "..tostring(obj:GetHashCode()).."  "..tostring(obj:GetType()).."  "..tostring(obj.name).."   "..tostring(pool).."   "..debug.traceback())
		}
	}
		
	public static string GetInfo()
	{
		string info = "ObjectPool:";
		Dictionary<object, PoolInfo>.Enumerator enumerator = recycledObjPoolMap.GetEnumerator();
		while (enumerator.MoveNext())
		{
			if (enumerator.Current.Value.pool.Count > 0 || enumerator.Current.Value.curVisibleNum > 0 || enumerator.Current.Value.maxVisibleNum > 0)
			{
				info += "\nkey: " + enumerator.Current.Key + "    pool: " + enumerator.Current.Value.pool.Count + "    max: " + enumerator.Current.Value.curVisibleNum + "  cur: " + enumerator.Current.Value.curVisibleNum;
			}
		}
		return info;
	}
}
